#include <model3d.h>
#include <scn.h>
#include <vector3.h>
#include <matrix4x4.h>
#include <transforms.h>
#include <render.h>

#include <visual.h>
#include <debug.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <SDL.h>

static const double ROT_HORZ_RADIANS = M_PI / 16.0;
static const double ROT_VERT_RADIANS = M_PI / 16.0;
static const double MOVE_AMOUNT = 10;

void
help( void )
{
  printf( "Help:\n"
	  " - F1: show this text.\n"
	  " - F2: toggle edges display.\n"
	  " - F3: Top tour.\n"
	  " - F4: Bottom tour.\n"
	  " - Q/W: increase/decrease maximum voxel intensity.\n"
	  " - q/w: increase/decrease minimum voxel intensity.\n"
	  "\n"
	  "   Camera operations:\n"
	  "    - UP/DOWN: move camera vertically.\n"
	  "    - LEFT/RIGHT: move camera horizontally.\n"
	  "   Extra plan operations:\n"
	  "    - 1/2: move extra plane to front/back.\n"
	  "    - 3/4: move extra plane to left/right.\n"
	  "    - 5/6: move extra plane to up/down.\n"
	  "    - 7/8: rotate extra plane around y.\n"
	  "    - 9/0: rotate extra plane around x.\n"
	  "   Plans Operation (model faces):\n"
	  "    - F/f: move front plane more to front or to back.\n"
	  "    - B/b: move back plane more to back or to front.\n"
	  "    - L/l: move left plane more to left or to right.\n"
	  "    - R/r: move right plane more to right or to left.\n"
	  "    - T/t: move top plane more to top or to bottom.\n"
	  "    - O/o: move bottom plane more to bottom or to top.\n"
	  "\n"
	  );
}

void
do_render( surface_t *surface,
	   const model3d_t *model,
	   const plane_t *planes,
	   const unsigned n_planes,
	   const double rot_y,
	   const double rot_x,
	   const color_t *edge_color,
	   const color_t *bg_color,
	   const uint16_t vmin,
	   const uint16_t vmax )
{
  render( surface, 0, 0,
	  model, planes, n_planes,
	  rot_y, rot_x,
	  edge_color, bg_color,
	  vmin, vmax );

}


void
do_tour_top( SDL_Surface *screen,
	     surface_t *surface,
	     const model3d_t *model,
	     const plane_t *planes,
	     const unsigned n_planes,
	     const color_t *edge_color,
	     const color_t *bg_color,
	     const uint16_t vmin,
	     const uint16_t vmax )
{
  double rot_y, rot_x=M_PI_4;
  for ( rot_y=0; rot_y < 2 * M_PI; rot_y += ROT_HORZ_RADIANS )
    {
      do_render( surface, model, planes, n_planes, rot_y, rot_x,
		 edge_color,  bg_color, vmin, vmax );
      SDL_Flip( screen );
    }
}

void
do_tour_bottom( SDL_Surface *screen,
		surface_t *surface,
		const model3d_t *model,
		const plane_t *planes,
		const unsigned n_planes,
		const color_t *edge_color,
		const color_t *bg_color,
		const uint16_t vmin,
		const uint16_t vmax )
{
  double rot_y, rot_x=-M_PI_4;
  for ( rot_y=0; rot_y < 2 * M_PI; rot_y += ROT_HORZ_RADIANS )
    {
      do_render( surface, model, planes, n_planes, rot_y, rot_x,
		 edge_color, bg_color, vmin, vmax );
      SDL_Flip( screen );
    }
}



int
main( int argc, char *argv[] )
{
  debug_level = DEBUG_ERR;

  if ( argc < 2 )
    {
      fprintf( stderr, "Missing SCN file.\n" );
      return -1;
    }


  model3d_t *model = scn_load( argv[ 1 ] );

  uint16_t vmin, vmax;
  find_intensities( model, &vmin, &vmax );

  uint16_t d = sqrt( pow2( model->size.x ) +
		     pow2( model->size.y ) +
		     pow2( model->size.z ) );


  if ( SDL_Init( SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE ) != 0)
    {
      err( "Could not initialize SDL: %s\n",  SDL_GetError() );
      return -1;
    }

  SDL_Surface *screen = SDL_SetVideoMode( d, d, 32,
					  SDL_SWSURFACE | SDL_DOUBLEBUF );

  if ( screen == NULL )
    {
      err( "Could not create screen %dx%d @ %dbpp.\n", d, d, 32 );
      return -1;
    }

  /* DIRTY OPTIMIZATION HACK
   * use SDL buffer as our surface buffer
   */
  surface_t surface;
  surface.size.x = screen->w;
  surface.size.y = screen->h;
  surface.buffer = screen->pixels;

  color_t edge_color, *pedge_color;
  color_t bg_color, *pbg_color;

  COLOR_FROM_ARGB( edge_color, COLOR_RED );
  COLOR_FROM_ARGB( bg_color, COLOR_YELLOW );

  pedge_color = &edge_color;
  pbg_color   = &bg_color;

  int x = model->size.x / 2 - 1;
  int y = model->size.y / 2 - 1;
  int z = model->size.z / 2 - 1;


  double rot_y=-M_PI_4, rot_x=M_PI_4;

  unsigned n_planes = 7;
  plane_t *planes = calloc( n_planes, sizeof( plane_t ) );
  planes[ 0 ].point  = vector3_init(  x,  0,  0 );
  planes[ 0 ].normal = vector3_init(  1,  0,  0 );

  planes[ 1 ].point  = vector3_init(  0,  y,  0 );
  planes[ 1 ].normal = vector3_init(  0,  1,  0 );

  planes[ 2 ].point  = vector3_init(  0,  0,  z );
  planes[ 2 ].normal = vector3_init(  0,  0,  1 );

  planes[ 3 ].point  = vector3_init( -x-1,  0,  0 );
  planes[ 3 ].normal = vector3_init( -1,  0,  0 );

  planes[ 4 ].point  = vector3_init(  0, -y-1,  0 );
  planes[ 4 ].normal = vector3_init(  0, -1,  0 );

  planes[ 5 ].point  = vector3_init(  0,  0, -z-1 );
  planes[ 5 ].normal = vector3_init(  0,  0, -1 );

  planes[ 6 ].point  = vector3_init(  0,  0, 0 );
  planes[ 6 ].normal = vector3_init(  0,  1, 1 );

  do_render( &surface, model, planes, n_planes,
	     rot_y, rot_x, pedge_color, pbg_color,
	     vmin, vmax );
  SDL_Flip( screen );


  matrix4x4_t *rot_y_pos = rotate_y( ROT_HORZ_RADIANS );
  matrix4x4_t *rot_y_neg = rotate_y( - ROT_HORZ_RADIANS );
  matrix4x4_t *rot_x_pos = rotate_x( ROT_VERT_RADIANS );
  matrix4x4_t *rot_x_neg = rotate_x( - ROT_VERT_RADIANS );


  help();

  int run = 1;
  int need_render = 0;
  while ( run )
    {
      SDL_Event e;
      SDL_WaitEvent( &e );
      switch ( e.type )
	{
	case SDL_KEYDOWN:
	  /* ignore keydown */
	  break;

	case SDL_KEYUP:
	  switch ( e.key.keysym.sym )
	    {
	    case SDLK_ESCAPE:
		run = 0;
	      break;

	    case SDLK_F1:
	      help();
	      break;

	    case SDLK_F2:
	      if ( pedge_color == NULL )
		pedge_color = &edge_color;
	      else
		pedge_color = NULL;
	      need_render = 1;
	      break;

	    case SDLK_q:
	      if ( e.key.keysym.mod & KMOD_SHIFT )
		{
		  if ( vmax + 1 < INT16_MAX )
		    {
		      vmax += 1;
		      need_render = 1;
		    }
		}
	      else
		{
		  if ( ( vmin + 1 < INT16_MAX ) && ( vmin + 1 < vmax ) )
		    {
		      vmin += 1;
		      need_render = 1;
		    }
		}
	      break;

	    case SDLK_w:
	      if ( e.key.keysym.mod & KMOD_SHIFT )
		{
		  if ( ( vmax > 0 ) && ( vmax > vmin + 1 ) )
		    {
		      vmax -= 1;
		      need_render = 1;
		    }
		}
	      else
		{
		  if ( vmin > 0 )
		    {
		      vmin -= 1;
		      need_render = 1;
		    }
		}
	      break;


	    case SDLK_LEFT:
	      rot_y += ROT_HORZ_RADIANS;
	      need_render = 1;
	      break;

	    case SDLK_RIGHT:
	      rot_y -= ROT_HORZ_RADIANS;
	      need_render = 1;
	      break;

	    case SDLK_UP:
	      rot_x += ROT_VERT_RADIANS;
	      need_render = 1;
	      break;

	    case SDLK_DOWN:
	      rot_x -= ROT_VERT_RADIANS;
	      need_render = 1;
	      break;

	    case SDLK_1:
	      if ( VECTOR3_Z( planes[ n_planes - 1 ].point ) <  z )
		{
		  VECTOR3_Z( planes[ n_planes - 1 ].point ) += MOVE_AMOUNT;
		  need_render = 1;
		}
	      break;

	    case SDLK_2:
	      if ( VECTOR3_Z( planes[ n_planes - 1 ].point ) >  -z )
		{
		  VECTOR3_Z( planes[ n_planes - 1 ].point ) -= MOVE_AMOUNT;
		  need_render = 1;
		}
	      break;

	    case SDLK_3:
	      if ( VECTOR3_X( planes[ n_planes - 1 ].point ) <  x )
		{
		  VECTOR3_X( planes[ n_planes - 1 ].point ) += MOVE_AMOUNT;
		  need_render = 1;
		}
	      break;

	    case SDLK_4:
	      if ( VECTOR3_X( planes[ n_planes - 1 ].point ) > -x )
		{
		  VECTOR3_X( planes[ n_planes - 1 ].point ) -= MOVE_AMOUNT;
		  need_render = 1;
		}
	      break;

	    case SDLK_5:
	      if ( VECTOR3_Y( planes[ n_planes - 1 ].point ) <  y )
		{
		  VECTOR3_Y( planes[ n_planes - 1 ].point ) += MOVE_AMOUNT;
		  need_render = 1;
		}
	      break;

	    case SDLK_6:
	      if ( VECTOR3_Y( planes[ n_planes - 1 ].point ) > -y )
		{
		  VECTOR3_Y( planes[ n_planes - 1 ].point ) -= MOVE_AMOUNT;
		  need_render = 1;
		}
	      break;

	    case SDLK_7:
	      {
		vector3_t r;
		multiply_matrix4x4_vector3( *rot_y_neg,
					    planes[ n_planes - 1 ].normal,
					    &r );
		planes[ n_planes - 1 ].normal = r;
		need_render = 1;
	      }
	      break;

	    case SDLK_8:
	      {
		vector3_t r;
		multiply_matrix4x4_vector3( *rot_y_pos,
					    planes[ n_planes - 1 ].normal,
					    &r );
		planes[ n_planes - 1 ].normal = r;
		need_render = 1;
	      }
	      break;

	    case SDLK_9:
	      {
		vector3_t r;
		multiply_matrix4x4_vector3( *rot_x_neg,
					    planes[ n_planes - 1 ].normal,
					    &r );
		planes[ n_planes - 1 ].normal = r;
		need_render = 1;
	      }
	      break;

	    case SDLK_0:
	      {
		vector3_t r;
		multiply_matrix4x4_vector3( *rot_x_pos,
					    planes[ n_planes - 1 ].normal,
					    &r );
		planes[ n_planes - 1 ].normal = r;
		need_render = 1;
	      }
	      break;

	    case SDLK_f:
	      {
		if ( e.key.keysym.mod & KMOD_SHIFT )
		  {
		    if ( VECTOR3_Z( planes[ 2 ].point ) < z )
		      {
			VECTOR3_Z( planes[ 2 ].point ) += MOVE_AMOUNT;
			need_render = 1;
		      }
		  }
		else
		  {
		    if ( VECTOR3_Z( planes[ 2 ].point ) > -z )
		      {
			VECTOR3_Z( planes[ 2 ].point ) -= MOVE_AMOUNT;
			need_render = 1;
		      }
		  }
	      }
	      break;

	    case SDLK_b:
	      {
		if ( e.key.keysym.mod & KMOD_SHIFT )
		  {
		    if ( VECTOR3_Z( planes[ 5 ].point ) > -z )
		      {
			VECTOR3_Z( planes[ 5 ].point ) -= MOVE_AMOUNT;
			need_render = 1;
		      }
		  }
		else
		  {
		    if ( VECTOR3_Z( planes[ 5 ].point ) < z )
		      {
			VECTOR3_Z( planes[ 5 ].point ) += MOVE_AMOUNT;
			need_render = 1;
		      }
		  }
	      }
	      break;

	    case SDLK_r:
	      {
		if ( e.key.keysym.mod & KMOD_SHIFT )
		  {
		    if ( VECTOR3_X( planes[ 0 ].point ) < x )
		      {
			VECTOR3_X( planes[ 0 ].point ) += MOVE_AMOUNT;
			need_render = 1;
		      }
		  }
		else
		  {
		    if ( VECTOR3_X( planes[ 0 ].point ) > -x )
		      {
			VECTOR3_X( planes[ 0 ].point ) -= MOVE_AMOUNT;
			need_render = 1;
		      }
		  }
	      }
	      break;

	    case SDLK_l:
	      {
		if ( e.key.keysym.mod & KMOD_SHIFT )
		  {
		    if ( VECTOR3_X( planes[ 3 ].point ) > -x )
		      {
			VECTOR3_X( planes[ 3 ].point ) -= MOVE_AMOUNT;
			need_render = 1;
		      }
		  }
		else
		  {
		    if ( VECTOR3_X( planes[ 3 ].point ) < x )
		      {
			VECTOR3_X( planes[ 3 ].point ) += MOVE_AMOUNT;
			need_render = 1;
		      }
		  }
	      }
	      break;

	    case SDLK_t:
	      {
		if ( e.key.keysym.mod & KMOD_SHIFT )
		  {
		    if ( VECTOR3_Y( planes[ 1 ].point ) < y )
		      {
			VECTOR3_Y( planes[ 1 ].point ) += MOVE_AMOUNT;
			need_render = 1;
		      }
		  }
		else
		  {
		    if ( VECTOR3_Y( planes[ 1 ].point ) > -y )
		      {
			VECTOR3_Y( planes[ 1 ].point ) -= MOVE_AMOUNT;
			need_render = 1;
		      }
		  }
	      }
	      break;

	    case SDLK_o:
	      {
		if ( e.key.keysym.mod & KMOD_SHIFT )
		  {
		    if ( VECTOR3_Y( planes[ 4 ].point ) > -y )
		      {
			VECTOR3_Y( planes[ 4 ].point ) -= MOVE_AMOUNT;
			need_render = 1;
		      }
		  }
		else
		  {
		    if ( VECTOR3_Y( planes[ 4 ].point ) < y )
		      {
			VECTOR3_Y( planes[ 4 ].point ) += MOVE_AMOUNT;
			need_render = 1;
		      }
		  }
	      }
	      break;

	    case SDLK_F3:
	      do_tour_top( screen, &surface, model, planes, n_planes,
			   pedge_color, pbg_color, vmin, vmax );
	      need_render = 1;
	      break;

	    case SDLK_F4:
	      do_tour_bottom( screen, &surface, model, planes, n_planes,
			      pedge_color, pbg_color, vmin, vmax );
	      need_render = 1;
	      break;



	    default:
	      break;

	    }
	  break;

	case SDL_ACTIVEEVENT:
	  SDL_Flip( screen );
	  break;

	case SDL_QUIT:
	  run = 0;
	  break;

	default:
	  break;
	}
      if ( need_render )
	{
	  do_render( &surface, model, planes, n_planes,
		     rot_y, rot_x, pedge_color, pbg_color,
		     vmin, vmax );
	  SDL_Flip( screen );
	  need_render = 0;
	}

    }

  matrix4x4_free( rot_y_pos );
  matrix4x4_free( rot_y_neg );
  matrix4x4_free( rot_x_pos );
  matrix4x4_free( rot_x_neg );

  free( planes );

  model3d_free( model );
  SDL_Quit();

  return 0;
}
